package spring.framework.beans.factory.xml;

import cn.hutool.core.bean.BeanException;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import spring.framework.beans.BeansException;
import spring.framework.beans.PropertyValue;
import spring.framework.beans.factory.config.BeanDefinition;
import spring.framework.beans.factory.config.BeanReference;
import spring.framework.beans.factory.support.AbstractBeanDefinitionReader;
import spring.framework.beans.factory.support.BeanDefinitionRegistry;
import spring.framework.core.io.Resource;
import spring.framework.core.io.ResourceLoader;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author fun.pengzh
 * @class spring.framework.beans.factory.xml.XmlBeanDefinitioinReader
 * @desc 获取Bean定义 默认实现
 * @since 2021-09-30
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        super(registry, resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(Resource resource) throws BeansException {
        try (InputStream inputStream = resource.getInputStream()) {
            doLoaderBeanDefinition(inputStream);
        } catch (IOException | ClassNotFoundException e) {
            throw new BeansException("Exception parsing xml document from '" + resource + "'");
        }
    }

    @Override
    public void loadBeanDefinitions(Resource... resources) throws BeansException {
        for (Resource resource : resources) {
            loadBeanDefinitions(resource);
        }
    }

    @Override
    public void loadBeanDefinitions(String location) throws BeanException {
        Resource resource = getResourceLoader().getResource(location);
        loadBeanDefinitions(resource);
    }

    @Override
    public void loadBeanDefinitions(String... locations) throws BeansException {
        for (String location : locations) {
            loadBeanDefinitions(location);
        }
    }

    private void doLoaderBeanDefinition(InputStream inputStream) throws ClassNotFoundException {
        Document document = XmlUtil.readXML(inputStream);
        Element root = document.getDocumentElement();
        NodeList childNodes = root.getChildNodes();

        for (int i = 0; i < childNodes.getLength(); i++) {
            Node childNode = childNodes.item(i);
            NodeList grandChildNodes = childNode.getChildNodes();

            if (!(childNode instanceof Element)) continue;
            if (!"bean".equals(childNode.getNodeName())) continue;

            Element child = (Element) childNode;
            String id = child.getAttribute("id");
            String name = child.getAttribute("name");
            String className = child.getAttribute("class");
            String initMethod = child.getAttribute("init-method");
            String destroyMethodName = child.getAttribute("destroy-method");

            Class<?> clazz = Class.forName(className);
            String beanName = StrUtil.isNotBlank(id) ? id : name;
            beanName = StrUtil.isBlank(beanName) ? clazz.getSimpleName() : beanName;

            //创建 Bean定义 对象
            BeanDefinition beanDefinition = new BeanDefinition(clazz);
            beanDefinition.setInitMethodName(initMethod);
            beanDefinition.setDestroyMethodName(destroyMethodName);

            //属性填充
            for (int j = 0; j < grandChildNodes.getLength(); j++) {
                Node grandChildNode = grandChildNodes.item(j);
                if (!(grandChildNode instanceof Element)) continue;
                if (!"property".equals(grandChildNode.getNodeName())) continue;

                Element grandChild = (Element) grandChildNode;
                String propertyName = grandChild.getAttribute("name");
                String propertyValue = grandChild.getAttribute("value");
                String propertyReference = grandChild.getAttribute("ref");

                Object value = StrUtil.isNotBlank(propertyReference) ? new BeanReference(propertyReference) : propertyValue;
                PropertyValue property = new PropertyValue(propertyName, value);
                beanDefinition.getPropertyValues().addPropertyValue(property);
                if (getRegistry().containsBeanDefinition(beanName)) {
                    throw new BeansException("Duplicate beanName [" + beanName + "] is not allowd");
                }
            }

            //Bean注册
            getRegistry().registerBeanDefinition(beanName, beanDefinition);
        }
    }
}
